<template>
  <div class="api-tester">
    <div class="panel request">
      <div class="topbar">
        <el-select v-model="form.method" size="mini" style="width: 100px">
          <el-option v-for="m in methods" :key="m" :label="m" :value="m" />
        </el-select>
        <el-input v-model="form.url" size="mini" placeholder="请输入请求地址" class="url-input" @blur="parseUrlParams" />
        <el-button type="primary" size="mini" :loading="loading" @click="send">发送</el-button>
        <el-button size="mini" @click="reset">重置</el-button>
        <el-dropdown @command="onCommand">
          <el-button size="mini">保存<i class="el-icon-arrow-down el-icon--right"></i></el-button>
          <el-dropdown-menu slot="dropdown">
            <el-dropdown-item command="saveCurrent">保存当前</el-dropdown-item>
            <el-dropdown-item command="show">查看已保存</el-dropdown-item>
            <el-dropdown-item command="export">导出JSON</el-dropdown-item>
            <el-dropdown-item command="import">导入JSON</el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
        <input ref="importInput" type="file" accept="application/json" @change="importCollections" style="display:none" />
      </div>
      <el-tabs v-model="reqTab" type="card" size="mini">
        <el-tab-pane label="Params" name="params">
          <div class="tab-scroll">
            <div class="kv-list">
              <div class="kv-row" v-for="(p,i) in form.params" :key="'p'+i">
                <el-checkbox v-model="p.enabled" />
                <el-input v-model="p.key" size="mini" placeholder="Key" class="kv-key" />
                <el-input v-model="p.value" size="mini" placeholder="Value" class="kv-val" />
                <el-button type="text" size="mini" @click="removeParam(i)">删除</el-button>
              </div>
              <el-button type="primary" plain size="mini" @click="addParam">新增参数</el-button>
            </div>
          </div>
        </el-tab-pane>
        <el-tab-pane label="Headers" name="headers">
          <div class="tab-scroll">
            <div class="kv-list">
              <div class="kv-row" v-for="(h,i) in form.headers" :key="'h'+i">
                <el-checkbox v-model="h.enabled" />
                <el-input v-model="h.key" size="mini" placeholder="Key" class="kv-key" />
                <el-input v-model="h.value" size="mini" placeholder="Value" class="kv-val" />
                <el-button type="text" size="mini" @click="removeHeader(i)">删除</el-button>
              </div>
              <el-button type="primary" plain size="mini" @click="addHeader">新增请求头</el-button>
            </div>
          </div>
        </el-tab-pane>
        <el-tab-pane label="Body" name="body">
          <div class="body-type">
            <el-radio-group v-model="form.bodyType" size="mini">
              <el-radio-button label="none">None</el-radio-button>
              <el-radio-button label="json">JSON</el-radio-button>
              <el-radio-button label="raw">Raw</el-radio-button>
              <el-radio-button label="form">form-data</el-radio-button>
              <el-radio-button label="form-urlencoded">x-www-form-urlencoded</el-radio-button>
            </el-radio-group>
          </div>

          <!-- JSON & Raw -->
          <div v-if="form.bodyType==='json' || form.bodyType==='raw'" class="tab-scroll">
            <el-input type="textarea" :rows="3" v-model="form.body" placeholder="请求体" />
          </div>

          <!-- form-data -->
          <div v-if="form.bodyType==='form'" class="tab-scroll">
            <div class="kv-list">
              <div class="kv-row" v-for="(f,i) in form.formData" :key="'fd'+i">
                <el-checkbox v-model="f.enabled" />
                <el-input v-model="f.key" size="mini" placeholder="Key" class="kv-key" />
                <el-input v-model="f.value" size="mini" placeholder="Value" class="kv-val" />
                <el-button type="text" size="mini" @click="removeFormData(i)">删除</el-button>
              </div>
              <el-button type="primary" plain size="mini" @click="addFormData">新增 form-data</el-button>
              <div class="tip">当前支持文本键值。需要上传文件时请告知，我可为你扩展。</div>
            </div>
          </div>

          <!-- x-www-form-urlencoded -->
          <div v-if="form.bodyType==='form-urlencoded'" class="tab-scroll">
            <div class="kv-list">
              <div class="kv-row" v-for="(u,i) in form.urlencoded" :key="'ue'+i">
                <el-checkbox v-model="u.enabled" />
                <el-input v-model="u.key" size="mini" placeholder="Key" class="kv-key" />
                <el-input v-model="u.value" size="mini" placeholder="Value" class="kv-val" />
                <el-button type="text" size="mini" @click="removeUrlencoded(i)">删除</el-button>
              </div>
              <el-button type="primary" plain size="mini" @click="addUrlencoded">新增 urlencoded</el-button>
            </div>
          </div>
        </el-tab-pane>
        <el-tab-pane label="Options" name="options">
          <div class="tab-scroll">
            <div class="options">
              <el-input-number v-model="form.timeout" :min="1000" :step="1000" size="mini" />
              <span class="opt-label">ms</span>
              <el-checkbox v-model="form.followRedirects">跟随重定向</el-checkbox>
            </div>
          </div>
        </el-tab-pane>
      </el-tabs>
    </div>

    <div class="panel response">
      <div class="response-header">
        <el-tabs v-model="activeTab" type="card" size="mini">
          <el-tab-pane label="Body" name="body" />
          <el-tab-pane label="Headers" name="headers" />
        </el-tabs>
        <div class="meta">
          <div>状态：<span>{{ result.status }}</span></div>
          <div>耗时：<span>{{ result.durationMs }}ms</span></div>
        </div>
      </div>
      <div class="tab-content">
        <div v-if="activeTab === 'body'">
          <div v-if="isBodyJson" class="body-scroll">
            <template v-if="!jsonTooLarge">
              <json-viewer :value="jsonParsed" :expand-depth="8" boxed sort copyable />
            </template>
            <pre v-else class="text">{{ prettyJson }}</pre>
          </div>
          <pre v-else class="text body-scroll">{{ result.body }}</pre>
        </div>
        <div v-if="activeTab === 'headers'" class="headers-view body-scroll">
          <div class="header-item" v-for="(vals, key) in result.headers" :key="key">
            <div class="k">{{ key }}</div>
            <div class="v">{{ vals.join(', ') }}</div>
          </div>
        </div>
      </div>
    </div>

    <div class="drawer right-drawer" v-show="showCollections">
      <div class="drawer-header">
        <span>已保存</span>
        <el-button type="text" size="mini" @click="toggleCollections">关闭</el-button>
      </div>
      
      <!-- 类别筛选工具栏 -->
      <div class="category-toolbar">
        <el-select v-model="categoryFilter" size="mini" placeholder="筛选类别" clearable>
          <el-option v-for="cat in categories" :key="cat" :label="cat" :value="cat" />
        </el-select>
        
        <el-input
          v-model="newCategory"
          size="mini"
          placeholder="新增类别"
          style="width: 100px; margin-left: 8px"
          @keyup.enter.native="addCategory"
        >
          <el-button slot="append" icon="el-icon-plus" @click="addCategory" />
        </el-input>
      </div>

      <div class="drawer-body">
        <div v-if="filteredCollections.length === 0" class="empty">
          {{ categoryFilter ? `没有"${categoryFilter}"类别的条目` : '暂无已保存条目' }}
        </div>
        <div v-else>
          <div class="saved-item" v-for="(it, idx) in filteredCollections" :key="it.id">
            <div class="item-content">
              <span class="name" @click="loadCollection(it)">{{ it.name }}</span>
              <span class="category-tag" :class="'category-' + it.category">{{ it.category }}</span>
            </div>
            <div class="ops">
              <el-button type="text" size="mini" @click="deleteCollection(idx)">删除</el-button>
            </div>
          </div>
        </div>
      </div>

      <!-- 类别管理区域 -->
      <div class="category-management" v-if="categories.length > 1">
        <div class="category-header">类别管理</div>
        <div class="category-list">
          <div class="category-item" v-for="cat in categories" :key="cat">
            <span class="category-name">{{ cat }}</span>
            <el-button 
              v-if="cat !== '默认'" 
              type="text" 
              size="mini" 
              icon="el-icon-delete" 
              @click="removeCategory(cat)"
            />
          </div>
        </div>
      </div>
    </div>

    <div class="drawer-toggle right-toggle" @click="toggleCollections">
      <i class="el-icon-arrow-left" v-if="showCollections"></i>
      <i class="el-icon-arrow-right" v-else></i>
    </div>

    <el-dialog :visible.sync="saveDialog" title="保存" width="30%" append-to-body>
      <div class="save-dialog-content">
        <el-input v-model="saveName" placeholder="输入名称" style="margin-bottom: 16px" />
        <el-select v-model="selectedCategory" placeholder="选择类别" size="mini">
          <el-option v-for="cat in categories" :key="cat" :label="cat" :value="cat" />
        </el-select>
        <div class="new-category-input" v-if="selectedCategory === '新建类别'">
          <el-input 
            v-model="newCategory" 
            placeholder="输入新类别名称" 
            size="mini" 
            style="margin-top: 8px"
          />
        </div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="saveDialog=false">取消</el-button>
        <el-button type="primary" @click="confirmSave">确定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import JsonViewer from 'vue-json-viewer'
import request from '@/utils/request'

export default {
  name: 'ApiTesterView',
  components: { JsonViewer },
  data() {
    return {
      methods: ['GET','POST','PUT','DELETE','PATCH','HEAD','OPTIONS'],
      form: {
        method: 'GET',
        url: '',
        params: [{ enabled: true, key: '', value: '' }],
        headers: [{ enabled: true, key: '', value: '' }],
        bodyType: 'none',
        body: '',
        formData: [{ enabled: true, key: '', value: '' }],
        urlencoded: [{ enabled: true, key: '', value: '' }],
        timeout: 15000,
        followRedirects: true
      },
      loading: false,
      reqTab: 'params',
      activeTab: 'body',
      result: { status: 0, statusText: '', headers: {}, body: '', durationMs: 0, finalURL: '' },
      jsonParsed: {},
      isBodyJson: false,
      jsonTooLarge: false,
      showCollections: false,
      collections: [],
      saveDialog: false,
      saveName: '',
      categories: ['默认', '开发', '测试', '生产'],
      selectedCategory: '默认',
      newCategory: '',
      categoryFilter: ''
    }
  },
  computed: {
    prettyJson() {
      try { return JSON.stringify(this.jsonParsed, null, 2) } catch(e) { return '' }
    },
    filteredCollections() {
      if (!this.categoryFilter) return this.collections
      return this.collections.filter(item => item.category === this.categoryFilter)
    }
  },
  methods: {
    selectMethod(val){ this.form.method = val },    onMethodClick(m){ this.form.method = m },
    addHeader() { this.form.headers.push({ enabled: true, key: '', value: '' }) },
    removeHeader(i) { this.form.headers.splice(i,1) },
    addParam() { this.form.params.push({ enabled: true, key: '', value: '' }) },
    removeParam(i) { this.form.params.splice(i,1) },
    addFormData(){ this.form.formData.push({ enabled:true, key:'', value:'' }) },
    removeFormData(i){ this.form.formData.splice(i,1) },
    addUrlencoded(){ this.form.urlencoded.push({ enabled:true, key:'', value:'' }) },
    removeUrlencoded(i){ this.form.urlencoded.splice(i,1) },
    reset() {
      this.form.method = 'GET'
      this.form.url = ''
      this.form.params = [{ enabled: true, key: '', value: '' }]
      this.form.headers = [{ enabled: true, key: '', value: '' }]
      this.form.bodyType = 'none'
      this.form.body = ''
      this.form.formData = [{ enabled:true, key:'', value:'' }]
      this.form.urlencoded = [{ enabled:true, key:'', value:'' }]
      this.form.timeout = 15000
      this.form.followRedirects = true
      this.result = { status: 0, statusText: '', headers: {}, body: '', durationMs: 0, finalURL: '' }
      this.jsonParsed = {}
      this.isBodyJson = false
      this.jsonTooLarge = false
    },
    parseUrlParams() {
      if (!this.form.url) return
      
      try {
        const url = new URL(this.form.url)
        const params = new URLSearchParams(url.search)
        
        // 清除现有的参数
        this.form.params = []
        
        // 添加URL中的参数
        params.forEach((value, key) => {
          this.form.params.push({ enabled: true, key, value })
        })
        
        // 如果没有参数，至少保留一个空行
        if (this.form.params.length === 0) {
          this.form.params.push({ enabled: true, key: '', value: '' })
        }
        
        // 移除URL中的查询参数部分，只保留基础URL
        this.form.url = url.origin + url.pathname
        
      } catch (error) {
        // URL解析失败，不做处理
        console.log('URL解析失败:', error)
      }
    },
    isJson(text) { return this.isBodyJson },
    async send() {
      if (!this.form.url) { this.$message.error('请输入请求地址'); return }
      this.loading = true
      const headers = {}
      this.form.headers.filter(h=>h.enabled && h.key).forEach(h=> headers[h.key] = h.value)
      let body = ''
      if (this.form.bodyType === 'json') body = this.form.body
      if (this.form.bodyType === 'raw') body = this.form.body
      let urlObj
      try { urlObj = new URL(this.form.url) } catch(e) { urlObj = new URL(this.form.url, 'http://local/') }
      this.form.params.filter(p=>p.enabled && p.key).forEach(p=> urlObj.searchParams.set(p.key, p.value))
      try {
        const res = await request.post('/proxy', {
          method: this.form.method,
          url: urlObj.toString().replace('http://local/',''),
          headers,
          body,
          bodyType: this.form.bodyType,
          formData: this.form.formData.filter(x=>x.enabled && x.key),
          urlencoded: this.form.urlencoded.filter(x=>x.enabled && x.key),
          timeout: this.form.timeout,
          followRedirects: this.form.followRedirects
        }, { baseURL: 'http://localhost:10086' })
        this.result = res.data || res
        this.activeTab = 'body'
        // 仅在请求完成后进行一次 JSON 判定/解析，避免在渲染期间重复解析导致交互阻塞
        this.isBodyJson = false
        this.jsonParsed = {}
        this.jsonTooLarge = false
        const text = typeof this.result.body === 'string' ? this.result.body : ''
        if (text) {
          const trimmed = text.trim()
          const looksJson = (trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))
          if (looksJson) {
            try {
              this.jsonParsed = JSON.parse(trimmed)
              this.isBodyJson = true
              this.jsonTooLarge = trimmed.length > 120000
            } catch(e) {
              this.isBodyJson = false
            }
          }
        }
      } finally { this.loading = false }
    },
    onCommand(cmd){

      if(cmd==='saveCurrent'){ 
        this.$nextTick(() => { this.openSaveDialog() })
      }
      else if(cmd==='show'){ this.toggleCollections() }
      else if(cmd==='export'){ this.exportCollections() }
      else if(cmd==='import'){ this.$refs.importInput && this.$refs.importInput.click() }
    },
    toggleCollections(){ this.showCollections = !this.showCollections },
    openSaveDialog(){ this.saveName=''; this.saveDialog=true },
    confirmSave(){ 
      if(!this.saveName){ this.$message.error('请输入名称'); return } 
      const item={ 
        id: Date.now(), 
        name:this.saveName, 
        category: this.selectedCategory,
        form: JSON.parse(JSON.stringify(this.form)) 
      }; 
      this.collections.unshift(item); 
      this.saveToStorage(); 
      this.persistCollections(); 
      this.saveDialog=false; 
      this.$message.success('保存成功') 
    },
    loadCollection(it){ this.form = JSON.parse(JSON.stringify(it.form)); this.showCollections=false },
    deleteCollection(idx){ this.collections.splice(idx,1); this.saveToStorage(); this.persistCollections() },
    exportCollections(){ const blob = new Blob([JSON.stringify({collections: this.collections, categories: this.categories})], {type:'application/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'apiTester.json'; a.click(); URL.revokeObjectURL(a.href) },
    importCollections(e){ const f=e.target.files[0]; if(!f) return; const reader=new FileReader(); reader.onload=()=>{ try{ const data=JSON.parse(reader.result); if(Array.isArray(data.collections)){ this.collections = data.collections; this.categories = data.categories || ['默认', '开发', '测试', '生产']; this.saveToStorage(); this.persistCollections() } }catch(err){ this.$message.error('导入失败') } }; reader.readAsText(f) },
    saveToStorage(){ 
      localStorage.setItem('apiTester.collections', JSON.stringify(this.collections));
      localStorage.setItem('apiTester.categories', JSON.stringify(this.categories));
    },
    async persistCollections(){ 
      try{ 
        await request.post('/apiTester/save', { 
          collections: this.collections,
          categories: this.categories 
        }, { baseURL: 'http://localhost:10086' }) 
      }catch(e){} 
    },
    async loadFromServer(){ 
      try{ 
        const res = await request.get('/apiTester/list', { baseURL: 'http://localhost:10086' }); 
        if(res && res.data && Array.isArray(res.data.collections)) { 
          this.collections = res.data.collections; 
          this.categories = res.data.categories || ['默认', '开发', '测试', '生产'];
          this.saveToStorage() 
        } 
      }catch(e){} 
    },
    addCategory() {
      if (this.newCategory && !this.categories.includes(this.newCategory)) {
        this.categories.push(this.newCategory)
        this.newCategory = ''
        this.saveToStorage()
        this.persistCollections()
      }
    },
    removeCategory(category) {
      if (category === '默认') {
        this.$message.warning('不能删除默认类别')
        return
      }
      this.$confirm(`确定要删除类别"${category}"吗？所有属于该类别的条目将被移动到"默认"类别`, '确认删除', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 将属于该类别的条目移动到默认类别
        this.collections = this.collections.map(item => 
          item.category === category ? Object.assign({}, item, { category: '默认' }) : item
        )
        this.categories = this.categories.filter(c => c !== category)
        this.saveToStorage()
        this.persistCollections()
        this.$message.success('类别已删除')
      }).catch(() => {})
    }
  },
  created(){ 
    const raw = localStorage.getItem('apiTester.collections'); 
    if(raw){ try{ this.collections = JSON.parse(raw) }catch(e){} }
    const cats = localStorage.getItem('apiTester.categories');
    if(cats){ try{ this.categories = JSON.parse(cats) }catch(e){} }
    this.loadFromServer() 
  }
}
</script>

<style scoped lang="scss">
.api-tester { 
  display: flex; 
  flex-direction: column; 
  gap: 12px; 
  padding: 12px; 
  height: 100vh; 
  box-sizing: border-box; 
  background: #f5f7fa;
  overflow: hidden;
}
.panel { 
  background: #fff; 
  border-radius: 6px; 
  padding: 12px; 
  box-shadow: 0 1px 6px 0 rgba(0,0,0,0.06);
  border: 1px solid #ebeef5;
}
.request { 
  flex: 0 0 auto;
}
.response { 
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.topbar { 
  display: flex; 
  align-items: center; 
  gap: 6px; 
  flex-wrap: wrap; 
  padding-bottom: 8px;
  border-bottom: 1px solid #f0f2f5;
  margin-bottom: 8px;
}
.url-input { 
  flex: 1; 
  min-width: 200px;
}
.kv-list { 
  margin-top: 6px;
}
.kv-row { 
  display: flex; 
  align-items: center; 
  gap: 6px; 
  margin-bottom: 6px; 
  padding: 4px;
  background: #fafbfc;
  border-radius: 4px;
  border: 1px solid #ebeef5;
}
.kv-key { 
  width: 30%; 
  min-width: 100px;
}
.kv-val { 
  flex: 1;
}
.body-type { 
  margin-bottom: 12px;
}
.options { 
  display: flex; 
  align-items: center; 
  gap: 12px; 
  margin-top: 12px;
  padding: 8px;
  background: #f9fafc;
  border-radius: 4px;
  border: 1px solid #ebeef5;
}
.opt-label { 
  color: #606266; 
  font-size: 13px;
  font-weight: 500;
}
.response-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  padding: 0 6px;
}
.meta { 
  display: flex; 
  gap: 16px; 
  font-size: 12px;
}
.meta div {
  display: flex;
  align-items: center;
  gap: 4px;
}
.meta span {
  font-weight: 600;
  color: #409eff;
}
.tab-content {
  margin-top: 0;
  flex: 1;
  overflow: auto; /* 只有内部 body-scroll 产生滚动 */
}
.body-scroll { 
  height: 100%; /* 填满父容器，产生单一滚动条 */
  overflow: auto; 
  border: 1px solid #e4e7ed; 
  border-radius: 4px; 
  padding: 12px;
  background: #fafbfc;
}
.text { 
  white-space: pre-wrap; 
  word-break: break-all; 
  background: #f8f9fa; 
  padding: 12px; 
  border-radius: 4px; 
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; 
  font-size: 12px;
  line-height: 1.4;
  color: #2c3e50;
}
.headers-view { 
  padding: 8px;
}
.header-item { 
  display:flex; 
  gap:12px; 
  margin-bottom:8px; 
  padding: 8px 12px;
  background: #f8f9fa;
  border-radius: 4px;
  border: 1px solid #ebeef5;
}
.header-item .k { 
  width: 30%; 
  color:#303133;
  font-weight: 500;
  min-width: 150px;
}
.header-item .v { 
  flex:1;
  color: #606266;
  word-break: break-all;
}
.drawer { 
  position: fixed; 
  left: 0; 
  top: 0; 
  height: 100vh; 
  width: 320px; 
  background: #fff; 
  border-right: 1px solid #dcdfe6; 
  padding: 20px; 
  z-index: 1000; 
  box-shadow: 2px 0 12px rgba(0,0,0,0.12);
  overflow-y: auto;
}
.right-drawer {
  left: auto;
  right: 0;
  border-right: none;
  border-left: 1px solid #dcdfe6;
  box-shadow: -2px 0 12px rgba(0,0,0,0.12);
}
.drawer-toggle { 
  position: fixed; 
  left: 0; 
  top: 50%; 
  transform: translateY(-50%); 
  background: #409eff; 
  color: #fff; 
  border: none; 
  padding: 10px 12px; 
  cursor: pointer; 
  z-index: 1001; 
  border-radius: 0 8px 8px 0; 
  font-weight: bold;
  font-size: 14px;
  box-shadow: 2px 0 8px rgba(64, 158, 255, 0.4);
  transition: all 0.2s;
}
.right-toggle {
  left: auto;
  right: 0;
  border-radius: 8px 0 0 8px;
  box-shadow: -2px 0 8px rgba(64, 158, 255, 0.4);
}
.drawer-toggle:hover {
  background: #66b1ff;
  padding-left: 14px;
}
.right-toggle:hover {
  padding-left: 12px;
  padding-right: 14px;
}
.drawer-header { 
  display:flex; 
  justify-content: space-between; 
  align-items: center; 
  margin-bottom: 20px; 
  padding-bottom: 12px; 
  border-bottom: 2px solid #e4e7ed;
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}
.category-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 16px;
  padding: 12px;
  background: #f8f9fa;
  border-radius: 6px;
  border: 1px solid #ebeef5;
}
.drawer-body {
  flex: 1;
  overflow-y: auto;
}
.saved-item { 
  display:flex; 
  justify-content: space-between; 
  align-items:center; 
  padding: 12px 16px; 
  margin-bottom: 8px;
  border-radius: 6px;
  border: 1px solid #ebeef5;
  transition: all 0.2s;
  cursor: pointer;
}
.saved-item:hover { 
  background: #f5f7fa; 
  border-color: #409eff;
  transform: translateX(4px);
}
.item-content {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
}
.saved-item .name { 
  cursor: pointer; 
  font-weight: 500;
  color: #303133;
  font-size: 14px;
}
.saved-item .name:hover { 
  color: #409eff; 
}
.category-tag {
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 12px;
  background: #e6f7ff;
  color: #1890ff;
  border: 1px solid #91d5ff;
}
.category-默认 {
  background: #f6ffed;
  color: #52c41a;
  border-color: #b7eb8f;
}
.category-开发 {
  background: #fff7e6;
  color: #fa8c16;
  border-color: #ffd591;
}
.category-测试 {
  background: #f9f0ff;
  color: #722ed1;
  border-color: #d3adf7;
}
.category-生产 {
  background: #fff2e8;
  color: #fa541c;
  border-color: #ffbb96;
}
.saved-item .ops {
  opacity: 0.6;
  transition: opacity 0.2s;
}
.saved-item:hover .ops {
  opacity: 1;
}
.empty {
  text-align: center;
  color: #909399;
  padding: 40px 20px;
  font-size: 14px;
}
.category-management {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid #ebeef5;
}
.category-header {
  font-size: 14px;
  font-weight: 600;
  color: #303133;
  margin-bottom: 12px;
}
.category-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.category-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 12px;
  background: #f8f9fa;
  border-radius: 4px;
  border: 1px solid #ebeef5;
}
.category-name {
  font-size: 13px;
  color: #606266;
}
.save-dialog-content {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.new-category-input {
  margin-top: 8px;
}
.import-label { 
  cursor: pointer 
}
.import-label input { 
  display:none 
}
.el-tabs {
  margin-top: 4px;
}
.el-tab-pane {
  padding-top: 8px;
}
  /* Params 区域：最多展示三行，超出滚动 */
  .tab-scroll {
    max-height: 156px; /* 约等于三行 mini 行高 */
    overflow-y: auto;
    border: 1px solid #e4e7ed;
    border-radius: 4px;
    padding: 8px;
    background: #fff;
  }
</style>